%{ open Ast %}

%token LPAREN RPAREN LBRACE RBRACE LBRACKET RBRACKET
%token SEMI COMMA DOT QUOTATION
%token PLUS MINUS TIMES DIVIDE MOD PLUSPLUS MINUSMINUS
%token PLUSEQ MINUSEQ TIMESEQ DIVIDEEQ MODEQ
%token EQ NEQ LT LEQ GT GEQ AND NOT OR ASSIGN
%token IF ELSE ELSEIF FOR WHILE RETURN 
%token INT VOID FLOAT BOOL 
%token <int> INTEGER
%token <string> ID
%token <string> DATATYPE
%token <string> GAMETYPE
%token EOF

%left PLUSEQ MINUSEQ
%left TIMESEQ DIVIDEEQ MODEQ
%right ASSIGN
%left OR AND EQ NEQ
%left LT GT LEQ GEQ
%right NOT
%left PLUS MINUS
%left TIMES DIVIDE MOD
%left PLUSPLUS MINUSMINUS
%nonassoc NOELSE
%nonassoc ELSE
%nonassoc LPAREN

%start program
%type <Ast.program> program

%%

program:
/* nothing */ { [], [] }
| program vdecl { ($2 :: fst $1), snd $1 }
| program fdecl { fst $1, ($2 :: snd $1) }

fdecl:
    DATATYPE ID LPAREN formals_opt RPAREN LBRACE vdecl_list stmt_list RBRACE
        { {    fname = $2;
            rettype = $1;
            formals = $4;
            locals = List.rev $7;
            body = List.rev $8 } }

formals_opt:
    /* nothing */ { [] }
    | formal_list { List.rev($1) }

formal_list:
    param_decl { [$1] }
    | formal_list COMMA param_decl { $3 :: $1 }
            
vdecl_list:
    /* nothing */    { [] }
    | vdecl_list vdecl { $2 :: $1 }

vdecl:
    DATATYPE ID SEMI { { varname = $2; vartype = $1 } }
    
param_decl:
    DATATYPE ID
        { {    paramname = $2;
            paramtype = $1 } }

stmt_list:
    /* nothing */ { [] }
    | stmt_list stmt { $2 :: $1 }
    
stmt:
        expr SEMI { Expr($1) }
    | RETURN expr_opt SEMI { Return($2) }
    | LBRACE stmt_list RBRACE { Block(List.rev $2) }
    | IF LPAREN expr RPAREN stmt %prec NOELSE { If($3, $5, Block([])) }
    | IF LPAREN expr RPAREN stmt ELSE stmt %prec ELSE { If($3, $5, $7) }
    | FOR LPAREN expr_opt SEMI expr_opt SEMI expr_opt RPAREN stmt { For($3, $5, $7, $9) }
    | WHILE LPAREN expr RPAREN stmt { While($3, $5) }

expr_opt:
    /* nothing */ { NoExpr }
    | expr { $1 }    

expr:
        ID { Id($1) }                                             // foo
    | ID DOT ID { Member($1, $3) }                                // player.speed
    | INTEGER { Integer($1) }                                     // 8
    | QUOTATION ID QUOTATION { Strings($2) }                      // "helloworld"
    | GAMETYPE { GameType($1) }                                   // _HARD
    | expr PLUS expr { BinOp($1, Add, $3) }                       // x + y
    | expr MINUS expr { BinOp($1, Sub, $3) }                      // x - y
    | expr TIMES expr { BinOp($1, Mult, $3) }                     // x * y
    | expr DIVIDE expr { BinOp($1, Div, $3) }                     // x / y
    | expr ASSIGN expr { Assign($1,$3) }                          // x = y
    | expr PLUSEQ expr { Assign($1, BinOp($1, Add, $3)) }         // x += y
    | expr MINUSEQ expr { Assign($1, BinOp($1, Sub, $3)) }        // x -= y
    | expr TIMESEQ expr { Assign($1, BinOp($1, Mult, $3)) }       // x *= y
    | expr DIVIDEEQ expr { Assign($1, BinOp($1, Div, $3)) }       // x /=y
    | expr MODEQ expr { Assign($1, BinOp($1, Mod, $3)) }          // x %= y
    | expr MOD expr { BinOp($1, Mod, $3) }                        // x % y
    | expr AND expr { BinOp($1, And, $3) }                        // x && y
    | expr OR expr { BinOp($1, Or, $3) }                          // x || y
    | expr EQ expr { BinOp($1, Eq, $3) }                          // x == y
    | expr NEQ expr { BinOp($1, NEq, $3) }                        // x != y
    | expr LT expr { BinOp($1, Less, $3) }                        // x < y
    | expr LEQ expr { BinOp($1, LEq, $3) }                        // x <= y
    | expr GT expr { BinOp($1, Greater, $3) }                     // x > y
    | expr GEQ expr { BinOp($1, GEq, $3) }                        // x >= y
    | NOT expr { Not($2) }                                        // !x                   
    | expr PLUSPLUS { Assign($1, BinOp($1, Add, Integer(1))) }    // x++
    | expr MINUSMINUS { Assign($1, BinOp($1, Sub, Integer(1))) }  // x--
    | LPAREN expr RPAREN { $2 }                                   // (x)
    | ID LPAREN actuals_opt RPAREN { Call($1, $3) }               // foo(...), function call

actuals_opt:
    /* nothing */ { [] }
    | actuals_list { List.rev $1 }

actuals_list:
    expr { [$1] }
    | actuals_list COMMA expr { $3 :: $1 }
